/*
 * @Author: lihaogn
 * @Date: 2021-08-31 21:13:16
 * @LastEditTime: 2022-11-06 22:47:19
 * @LastEditor: lihaogn
 * @Description: webpack 生产环境配置文件 - 多入口文件
 * @FilePath: \webpack-learn\config\webpack.multi.prod.js
 */

const os = require("os");
// Node.js的核心模块，专门用来处理文件路径
const path = require("path");
// 处理 html 资源
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 配置 eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 提取 css 成单独文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// css 压缩
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 多线程打包用
const TerserPlugin = require("terser-webpack-plugin");
// preload 预加载
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核数-这边得到的是线程数
const threads = os.cpus().length;
// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
  // 入口文件
  // 相对路径和绝对路径都行
  entry: {
    main: "./src/js/entry/main.js",
    app: "./src/js/entry/app.js",
  },
  // 输出
  output: {
    // path: 文件输出目录，必须是绝对路径
    // path.resolve() 方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "../dist"),
    // filename: 输出文件名(目录)
    // [name]是webpack命名规则，使用chunk的name作为输出的文件名。
    // 什么是chunk？打包的资源就是chunk，输出出去叫bundle。
    // chunk的name是啥呢？ 比如： entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx，和文件名无关。
    // 为什么需要这样命名呢？如果还是之前写法main.js，那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    filename: "static/js/[name].[contenthash:8].js",
    chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式（注意用hash）
    // 自动将上次打包目录资源清空
    clean: true,
  },
  module: {
    rules: [
      {
        oneOf: [
          {
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            // use: ["style-loader", "css-loader"],
            // use: [MiniCssExtractPlugin.loader, "css-loader"],
            use: getStyleLoaders(),
          },
          {
            test: /\.less$/,
            // use: ["style-loader", "css-loader", "less-loader"],
            // use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
            use: getStyleLoaders("less-loader"),
          },
          {
            test: /\.s[ac]ss$/,
            // use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
            use: getStyleLoaders("sass-loader"),
          },
          {
            // 处理图片资源
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            // 默认小于 8kb 的文件将会视为 inline 模块类型，否则会被视为 resource 模块类型
            // inline 类型的会作为 dataURI 内联到 bundle 中
            // resource 类型的是作为一个单独的文件
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024, // 小于 10kb 的图片会被 base64 处理
              },
            },
            // 使用 output 中的 assetModuleFilename 替代
            // generator: {
            //   // 将图片文件输出到 static/imgs 目录中
            //   // 将图片文件命名 [hash:8][ext][query]
            //   // [hash:8]: hash 值取8位
            //   // [ext]: 使用之前的文件扩展名
            //   // [query]: query 参数，访问URL上的
            //   filename: "static/imgs/[hash:8][ext][query]",
            // },
          },
          {
            // 处理字体图标资源和其他资源
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            // 使用 output 中的 assetModuleFilename 替代
            // generator: {
            //   filename: "static/media/[hash:8][ext][query]",
            // },
          },
          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules代码不编译
            use: [
              {
                loader: "thread-loader", // 开启多进程
                options: {
                  workers: threads, // 数量
                },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  // babel辅助代码作为独立模块避免重复引入，减少代码体积
                  plugins: ["@babel/plugin-transform-runtime"],
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    // 配置 eslint
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
      threads, // 开启多进程
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/[name].[contenthash:8].css",
      chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
    }),
    // css压缩
    // new CssMinimizerPlugin(), // 写到 optimization 中去，这里注释掉
    // preload 预加载
    new PreloadWebpackPlugin({
      rel: "preload", // preload兼容性更好
      as: "script",
      // rel: 'prefetch' // prefetch兼容性更差
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [
      // css压缩也可以写到 optimization.minimizer 里面，效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启 TerserPlugin，但是我们需要进行配置，就要重新写了
      new TerserPlugin({
        parallel: threads, // 开启多进程
      }),
    ],
    // 代码分割配置，提取重复引入的代码和动态导入的文件
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize，最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数，满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包（此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests）

      // cacheGroups: { // 组，哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重（越大越高）
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块，则它将被重用，而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },

      // 修改配置，分组配置
      cacheGroups: {
        // 组，哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重（越大越高）
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块，则它将被重用，而不是生成新的模块
        // },
        default: {
          // 其他没有写的配置会使用上面面的默认值
          minSize: 0, // 我们定义的文件体积太小了，所以要改打包的最小文件体积
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
          name: "vendor",
        },
      },
    },
    // 提取runtime文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime 文件命名规则
    },
  },
  // 包含行/列映射
  devtool: "source-map",
  mode: "production",
};
